-
Notifications
You must be signed in to change notification settings - Fork 2
✨ Feat: 카드 수정 모달 구현 #91
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨ Feat: 카드 수정 모달 구현 #91
Conversation
Walkthrough이 변경 사항은 대시보드의 카드 수정 및 생성 모달 기능을 대대적으로 확장합니다. 카드 수정 폼, 컬럼/담당자 선택, 태그 삭제, 이미지 업로드, 글로벌 컬럼 스토어 도입, 카드 상세 모달, 다양한 UI 컴포넌트 및 스타일 유틸리티가 추가 및 개선되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Card
participant CardModal
participant CardContent
participant ModifyCardForm
participant API
User->>Card: 카드 클릭
Card->>CardModal: CardModal 오픈
CardModal->>CardContent: 카드 상세 정보 렌더
User->>CardContent: 수정 버튼 클릭
CardContent->>ModifyCardForm: 수정 폼 모달 오픈
User->>ModifyCardForm: 폼 입력(컬럼, 담당자, 태그, 이미지 등)
User->>ModifyCardForm: 저장 클릭
ModifyCardForm->>API: putCard API 호출
API-->>ModifyCardForm: 응답(성공/실패)
ModifyCardForm->>CardContent: onClose 호출(성공 시)
CardContent->>CardModal: 모달 닫기
Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
npm error Exit handler never called! ✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
🧹 Nitpick comments (17)
tailwind.config.ts (1)
21-21: 주석과 구현 간 불일치 수정 필요주석에는 "376 ~ 1919px"라고 되어 있지만, 실제 구현은 376px에서 744px까지의 범위입니다. 주석을 올바르게 수정해주세요.
- tablet: { raw: '(min-width: 376px) and (max-width: 744px)' }, // 376 ~ 1919px + tablet: { raw: '(min-width: 376px) and (max-width: 744px)' }, // 376 ~ 744pxsrc/app/features/dashboard_Id/Card/ColumnTitle.tsx (2)
1-1: Props 타입을 별도 인터페이스로 정의 권장재사용성과 가독성을 위해 props 타입을 별도 인터페이스로 정의하는 것을 고려해보세요.
+interface ColumnTitleProps { + title: string +} + -export default function ColumnTitle({ title }: { title: string }) { +export default function ColumnTitle({ title }: ColumnTitleProps) {
4-4: 스타일 일관성을 위한 커스텀 클래스 사용 권장하드코딩된 색상값
#228DFF대신 기존 커스텀 유틸리티 클래스를 사용하여 일관성을 유지하는 것이 좋겠습니다.- <div className="size-6 rounded-25 bg-[#228DFF]"></div> + <div className="size-6 rounded-25 bg-[#228DFF]"></div>현재 globals.css에서 같은 색상이 사용되는 다른 클래스들이 있으니 통일된 스타일 클래스를 만드는 것을 고려해보세요.
src/app/features/dashboard_Id/Card/cardModal/CardModal.tsx (1)
12-12: 접근성을 위한 속성 추가 권장모달의 접근성을 향상시키기 위해 ARIA 속성을 추가하는 것을 고려해보세요.
<div className="BG-white min-h-764 w-730 overflow-y-scroll rounded-16 p-32 shadow-lg [mask-image:radial-gradient(white_100%,transparent_100%)]"> + <div + className="BG-white min-h-764 w-730 overflow-y-scroll rounded-16 p-32 shadow-lg [mask-image:radial-gradient(white_100%,transparent_100%)]" + role="dialog" + aria-modal="true" + >src/app/features/dashboard_Id/api/usePutCardMutation.ts (4)
5-5: 사용하지 않는 import 제거 필요
postCardimport가 코드에서 사용되지 않습니다.-import { postCard } from './postCard'
8-8: 주석 내용 수정 필요주석에서 "카드 생성 모달"이라고 되어 있는데, 이 훅은 카드 수정을 위한 것입니다.
-// ✅ 카드 생성 모달에서 사용 (CreateCardForm.tsx) +// ✅ 카드 수정 모달에서 사용 (ModifyCardForm.tsx)
21-21: 쿼리 무효화 주석 수정 필요주석에서
'columnId'쿼리 무효화라고 되어 있지만 실제로는'cardId'쿼리를 무효화하고 있습니다.- queryClient.invalidateQueries({ queryKey: ['cardId'] }) //'columnId' 쿼리 invalidate - 카드가 stale 상태임을 알리고 다시 fetch 하도록 유도함 + queryClient.invalidateQueries({ queryKey: ['cardId'] }) //'cardId' 쿼리 invalidate - 카드가 stale 상태임을 알리고 다시 fetch 하도록 유도함
26-28: 에러 메시지 내용 수정 필요에러 메시지에서 "카드 생성 실패"라고 되어 있는데, 이 훅은 카드 수정을 위한 것입니다.
- console.error('카드 생성 실패:', message ?? '알 수 없는 에러') + console.error('카드 수정 실패:', message ?? '알 수 없는 에러') } else { - console.error('카드 생성 실패:', error) + console.error('카드 수정 실패:', error)src/app/features/dashboard_Id/Card/cardFormModals/AssigneeList.tsx (2)
3-3: 불필요한 Avatar 컴포넌트 import 제거
Avatar컴포넌트를 import하고 있지만MyAssignee컴포넌트가 아바타 렌더링을 담당하므로 사용되지 않습니다.-import { Avatar } from '@/app/shared/components/common/Avatar'
21-21: 주석 업데이트 필요주석에서 여전히
userId를 언급하고 있지만 실제 코드는id를 사용합니다.-// - controlField.onChange(assignee.userId) 실행 → react-hook-form에 userId 값을 전달 (form 제출에는 Id 데이터만 전달함) +// - controlField.onChange(assignee.id) 실행 → react-hook-form에 id 값을 전달 (form 제출에는 Id 데이터만 전달함)src/app/features/dashboard_Id/Card/Card.tsx (1)
27-27: JSON.stringify 에러 처리 추가
data-card-data속성에서 JSON.stringify가 실패할 수 있습니다. 순환 참조나 직렬화 불가능한 값이 있을 경우를 대비해야 합니다.- data-card-data={JSON.stringify(card)} + data-card-data={(() => { + try { + return JSON.stringify(card) + } catch { + return JSON.stringify({ id: card.id, title: card.title }) + } + })()}src/app/shared/components/common/Avatar.tsx (1)
39-39: 사용자 미인증 시 조기 반환 로직 검토props로 name과 imageUrl이 제공되어도 user가 없으면 null을 반환합니다. 이는 의도된 동작인지 확인이 필요합니다.
다음 시나리오에서 Avatar 컴포넌트가 어떻게 동작해야 하는지 명확히 하세요:
- 사용자가 미인증 상태이지만 명시적인 name/imageUrl props가 제공된 경우
- 인증된 사용자가 있고 props도 제공된 경우
만약 props가 제공되면 인증 상태와 무관하게 렌더링해야 한다면:
- if (!user) return null // user가 없으면 렌더링하지 않음 + if (!user && !name) return null // user와 name 모두 없으면 렌더링하지 않음src/app/features/dashboard_Id/Card/cardFormModals/ColumnList.tsx (1)
35-35: 주석 오류 수정 필요주석에서 "담당자 업데이트"라고 되어 있지만, 실제로는 컬럼을 업데이트하는 로직입니다.
- setColumn(column) // 담당자 업데이트 + setColumn(column) // 컬럼 업데이트src/app/features/dashboard_Id/Card/cardFormModals/ModifyCardForm.tsx (2)
81-81: 디버그 콘솔 로그 제거개발 중 디버깅용 console.log가 남아있습니다. 프로덕션 코드에서는 제거해야 합니다.
- console.log(tags)
121-121: 디버그 콘솔 로그 제거프로덕션 환경에서는 불필요한 디버그 로그를 제거해야 합니다.
- console.log('submitted', payload)src/app/features/dashboard_Id/Card/cardModal/CardContent.tsx (2)
28-28: CSS 클래스명 오타 수정
bt-24는 유효하지 않은 Tailwind CSS 클래스로 보입니다. 아마도mb-24(margin-bottom)를 의도한 것 같습니다.- <h2 className="Text-black bt-24 text-24 font-bold">{title}</h2> + <h2 className="Text-black mb-24 text-24 font-bold">{title}</h2>
60-60: 디버그 콘솔 로그 제거개발 중 디버깅용 console.log가 남아있습니다. 프로덕션 코드에서는 제거해야 합니다.
onClick={() => { onClose() - console.log('sdfadfdsgreggsfds') }}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
public/images/arrow-dropdown.svgis excluded by!**/*.svgpublic/images/close.svgis excluded by!**/*.svgpublic/images/drop-more.svgis excluded by!**/*.svg
📒 Files selected for processing (24)
src/app/dashboard/[id]/page.tsx(3 hunks)src/app/features/dashboard_Id/Card/Card.tsx(3 hunks)src/app/features/dashboard_Id/Card/ColumnTitle.tsx(1 hunks)src/app/features/dashboard_Id/Card/MyAssignee.tsx(1 hunks)src/app/features/dashboard_Id/Card/Tags.tsx(1 hunks)src/app/features/dashboard_Id/Card/TagsCanDelete.tsx(1 hunks)src/app/features/dashboard_Id/Card/cardFormModals/AssigneeList.tsx(2 hunks)src/app/features/dashboard_Id/Card/cardFormModals/ColumnList.tsx(1 hunks)src/app/features/dashboard_Id/Card/cardFormModals/CreateCardForm.tsx(3 hunks)src/app/features/dashboard_Id/Card/cardFormModals/CreateCardModal.tsx(0 hunks)src/app/features/dashboard_Id/Card/cardFormModals/ModifyCardForm.tsx(1 hunks)src/app/features/dashboard_Id/Card/cardFormModals/input/DateInput.tsx(1 hunks)src/app/features/dashboard_Id/Card/cardModal/CardContent.tsx(1 hunks)src/app/features/dashboard_Id/Card/cardModal/CardModal.tsx(1 hunks)src/app/features/dashboard_Id/Column/Column.tsx(3 hunks)src/app/features/dashboard_Id/api/putCard.ts(1 hunks)src/app/features/dashboard_Id/api/usePutCardMutation.ts(1 hunks)src/app/features/dashboard_Id/lib/getDashboardMembers.ts(1 hunks)src/app/features/dashboard_Id/store/useColumnsStore.ts(1 hunks)src/app/features/dashboard_Id/store/useDragStore.ts(1 hunks)src/app/features/dashboard_Id/type/CardFormData.type.ts(1 hunks)src/app/globals.css(3 hunks)src/app/shared/components/common/Avatar.tsx(2 hunks)tailwind.config.ts(1 hunks)
💤 Files with no reviewable changes (1)
- src/app/features/dashboard_Id/Card/cardFormModals/CreateCardModal.tsx
🧰 Additional context used
🧬 Code Graph Analysis (11)
src/app/features/dashboard_Id/lib/getDashboardMembers.ts (2)
src/app/features/dashboard_Id/type/Member.type.ts (1)
Member(1-10)src/app/features/dashboard_Id/type/Card.type.ts (1)
Assignee(1-5)
src/app/features/dashboard_Id/Card/MyAssignee.tsx (2)
src/app/features/dashboard_Id/type/Card.type.ts (1)
Assignee(1-5)src/app/shared/components/common/Avatar.tsx (1)
Avatar(34-77)
src/app/features/dashboard_Id/api/putCard.ts (1)
src/app/features/dashboard_Id/type/CardFormData.type.ts (1)
CardModifyFormData(1-9)
src/app/features/dashboard_Id/api/usePutCardMutation.ts (2)
src/app/features/dashboard_Id/type/CardFormData.type.ts (1)
CardModifyFormData(1-9)src/app/features/dashboard_Id/api/putCard.ts (1)
putCard(10-19)
src/app/features/dashboard_Id/Card/Tags.tsx (1)
src/app/features/dashboardId/Card/Tags.tsx (1)
Tags(3-29)
src/app/features/dashboard_Id/Card/cardFormModals/CreateCardForm.tsx (2)
src/app/shared/lib/cn.ts (1)
cn(4-6)src/app/features/dashboard_Id/Card/TagsCanDelete.tsx (1)
TagsCanDelete(5-49)
src/app/dashboard/[id]/page.tsx (1)
src/app/features/dashboard_Id/store/useColumnsStore.ts (1)
useColumnsStore(14-17)
src/app/features/dashboard_Id/Card/cardFormModals/AssigneeList.tsx (1)
src/app/features/dashboard_Id/Card/MyAssignee.tsx (1)
MyAssignee(5-18)
src/app/features/dashboard_Id/Card/Card.tsx (4)
src/app/features/dashboard_Id/type/Card.type.ts (1)
Card(6-19)src/app/shared/components/common/Avatar.tsx (1)
Avatar(34-77)src/app/features/dashboard_Id/Card/cardModal/CardModal.tsx (1)
CardModal(6-18)src/app/features/dashboard_Id/Card/cardModal/CardContent.tsx (1)
CardContent(16-91)
src/app/features/dashboard_Id/Card/cardFormModals/ColumnList.tsx (4)
src/app/features/dashboard_Id/store/useColumnsStore.ts (2)
SimpleColumn(3-6)useColumnsStore(14-17)src/app/features/dashboard_Id/type/CardFormData.type.ts (1)
CardFormData(11-13)src/app/shared/lib/cn.ts (1)
cn(4-6)src/app/features/dashboard_Id/Card/ColumnTitle.tsx (1)
ColumnTitle(1-10)
src/app/features/dashboard_Id/Card/cardFormModals/ModifyCardForm.tsx (13)
src/app/features/dashboard_Id/store/useColumnsStore.ts (1)
SimpleColumn(3-6)src/app/features/dashboard_Id/api/useUploadCardImage.ts (1)
useUploadCardImage(8-19)src/app/features/dashboard_Id/api/useMembers.ts (1)
useMembers(7-12)src/app/features/dashboard_Id/type/Card.type.ts (1)
Assignee(1-5)src/app/features/dashboard_Id/type/CardFormData.type.ts (2)
CardFormData(11-13)CardModifyFormData(1-9)src/app/features/dashboard_Id/api/usePutCardMutation.ts (1)
usePutCardMutation(9-32)src/app/features/dashboard_Id/Card/cardFormModals/input/Input.tsx (1)
Input(9-28)src/app/features/dashboard_Id/Card/ColumnTitle.tsx (1)
ColumnTitle(1-10)src/app/shared/lib/cn.ts (1)
cn(4-6)src/app/features/dashboard_Id/Card/cardFormModals/ColumnList.tsx (1)
ColumnList(19-44)src/app/features/dashboard_Id/Card/MyAssignee.tsx (1)
MyAssignee(5-18)src/app/features/dashboard_Id/Card/cardFormModals/AssigneeList.tsx (1)
AssigneeList(23-49)src/app/features/dashboard_Id/Card/TagsCanDelete.tsx (1)
TagsCanDelete(5-49)
🪛 Biome (1.9.4)
src/app/features/dashboard_Id/Card/cardFormModals/ModifyCardForm.tsx
[error] 122-122: Avoid the delete operator which can impact performance.
Unsafe fix: Use an undefined assignment instead.
(lint/performance/noDelete)
[error] 126-128: Avoid the delete operator which can impact performance.
Unsafe fix: Use an undefined assignment instead.
(lint/performance/noDelete)
🔇 Additional comments (23)
src/app/features/dashboard_Id/api/putCard.ts (1)
1-3: 프로덕션용 API 클라이언트 사용 확인 필요현재
testAxios를 사용하고 있고 정규axios는 주석 처리되어 있습니다. 프로덕션 환경에서는 정규 API 클라이언트를 사용해야 합니다.다음 스크립트로 다른 API 함수들에서 어떤 클라이언트를 사용하는지 확인해보세요:
#!/bin/bash # API 함수들에서 사용되는 import 패턴 확인 rg -A 2 -B 2 "import.*from.*axios" src/app/features/dashboard_Id/api/src/app/globals.css (4)
22-22: 다크모드 색상 업데이트 적절함BG-white 클래스의 다크모드 배경색상이 더 어두운 톤으로 변경되어 대비가 개선되었습니다.
30-32: 새로운 유틸리티 클래스 추가 적절함BG-lightblue 클래스가 일관된 네이밍 컨벤션을 따르며, 라이트/다크 모드를 모두 고려하여 잘 구현되었습니다.
57-59: 텍스트 색상 유틸리티 클래스 추가 적절함Text-deepblue 클래스가 기존 색상 팔레트와 일관성을 유지하며, 접근성을 고려한 색상 조합으로 구성되었습니다.
101-101: 컴포넌트별 스타일링 분리 적절함Input-readOnly에서 고정 너비를 제거하고 컴포넌트 레벨에서 관리하도록 변경한 것은 재사용성과 유연성을 향상시키는 좋은 접근입니다.
src/app/features/dashboard_Id/Card/cardFormModals/input/DateInput.tsx (2)
10-11: props spread 이후에 ref를 배치한 것이 좋습니다.props가 ref를 덮어쓰는 것을 방지하기 위해 ref를 spread operator 이후에 배치한 것이 올바른 패턴입니다.
14-14: 일관된 너비 스타일링을 위한 w-520 클래스 추가가 적절합니다.다른 폼 입력 필드들과 일관된 너비를 유지하기 위한 스타일링 개선입니다.
src/app/features/dashboard_Id/store/useColumnsStore.ts (1)
1-17: Zustand 스토어 구현이 깔끔하고 적절합니다.SimpleColumn 인터페이스와 ColumnsStore의 구조가 명확하고, Zustand의 표준 패턴을 잘 따르고 있습니다. 카드 수정 모달에서 사용할 간소화된 컬럼 데이터 관리에 적합한 설계입니다.
src/app/features/dashboard_Id/store/useDragStore.ts (1)
3-3: 타입 파일 임포트 경로 업데이트가 적절합니다.Card.type으로의 임포트 경로 변경이 타입 파일 네이밍 컨벤션과 일치하며, 코드베이스의 일관성을 향상시킵니다.
src/app/features/dashboard_Id/lib/getDashboardMembers.ts (3)
1-1: 타입 임포트 중앙화가 좋습니다.로컬 Assignee 인터페이스를 제거하고 Card.type에서 임포트하는 것이 코드 일관성과 유지보수성을 향상시킵니다.
11-11: profileImageUrl 추가가 UI 개선에 도움됩니다.MyAssignee 컴포넌트에서 아바타 표시를 위해 profileImageUrl 필드를 추가한 것이 적절합니다.
9-11: ```shell
#!/bin/bashMember 인터페이스 확인
sed -n '1,200p' src/app/features/dashboard_Id/type/Member.type.ts
Assignee 타입 확인
sed -n '1,200p' src/app/features/dashboard_Id/type/Card.type.ts
</details> <details> <summary>src/app/features/dashboard_Id/Card/MyAssignee.tsx (1)</summary> `1-18`: **MyAssignee 컴포넌트 구현이 깔끔합니다.** 담당자 정보를 아바타와 닉네임으로 표시하는 간단하고 재사용 가능한 컴포넌트입니다. Avatar 컴포넌트와의 통합도 적절하고, 스타일링도 일관성 있게 적용되었습니다. </details> <details> <summary>src/app/features/dashboard_Id/Column/Column.tsx (3)</summary> `81-82`: **스타일링 개선 승인** 기존의 하드코딩된 배경색을 `.BG-lightblue` 클래스로 변경한 것은 CSS 유틸리티 활용 측면에서 좋은 개선입니다. --- `92-92`: **Card 컴포넌트 props 변경 승인** 전체 `column` 객체를 전달하는 것은 Card 컴포넌트가 컬럼 정보에 더 쉽게 접근할 수 있게 해주는 좋은 변경입니다. --- `97-102`: **CreateCardModal 사용법 간소화 승인** `onClose` prop 제거로 모달 사용법이 간소화된 것은 좋은 개선입니다. </details> <details> <summary>src/app/features/dashboard_Id/type/CardFormData.type.ts (1)</summary> `1-13`: **인터페이스 분리 리팩토링 승인** `CardFormData`를 `CardModifyFormData`와 `CardFormData`로 분리한 것은 관심사 분리와 코드 재사용성 측면에서 훌륭한 리팩토링입니다. 카드 수정 시에는 `dashboardId`가 필요하지 않으므로 이를 분리한 것이 적절합니다. </details> <details> <summary>src/app/dashboard/[id]/page.tsx (2)</summary> `28-36`: **전역 스토어 동기화 로직 승인** columns 데이터를 전역 스토어와 동기화하는 useEffect 구현이 적절합니다. 데이터 변환 로직과 의존성 배열 설정이 올바릅니다. --- `144-144`: **반응형 레이아웃 개선 승인** `mobile:flex-row`를 추가하여 모바일 환경에서의 레이아웃을 개선한 것은 좋은 UX 개선입니다. </details> <details> <summary>src/app/features/dashboard_Id/Card/cardFormModals/CreateCardForm.tsx (2)</summary> `118-132`: **드롭다운 UI 개선 승인** 담당자 입력 필드에 고정 너비 적용과 회전 애니메이션이 적용된 드롭다운 화살표 추가는 훌륭한 UX 개선입니다. `cn` 유틸리티를 활용한 조건부 스타일링도 적절합니다. --- `221-221`: **TagsCanDelete 컴포넌트 적용 승인** 기존 `Tags` 컴포넌트를 `TagsCanDelete`로 변경하여 태그 삭제 기능을 추가한 것은 사용자 경험을 크게 향상시키는 좋은 개선입니다. </details> <details> <summary>src/app/features/dashboard_Id/Card/Tags.tsx (1)</summary> `25-28`: **다크 모드에서 텍스트 색상 대비 확인 필요** 다크 모드에서 텍스트 색상으로 `bgColors`를 사용하면 가독성 문제가 발생할 수 있습니다. 밝은 배경색이 다크 배경에서 충분한 대비를 제공하는지 확인이 필요합니다. 다음 스크립트로 색상 대비를 확인해보세요: ```shell #!/bin/bash # Description: 다크 모드 색상 조합의 대비 확인 echo "다크 모드 배경색들:" echo "#774212, #366712, #711E5C, #0F3167" echo "" echo "텍스트로 사용되는 색상들:" echo "#F9EEE3, #E7F7DB, #F7DBF0, #DBE6F7" echo "" echo "이 조합들이 WCAG 접근성 기준을 만족하는지 확인하세요."src/app/features/dashboard_Id/Card/cardFormModals/AssigneeList.tsx (1)
38-41: 타입 정의와 필드 사용처를 정확히 확인하기 위해 아래 스크립트를 실행해 주세요.#!/bin/bash # CardFormData 인터페이스 확인 rg "interface CardFormData" -A 5 -g "*.ts" -g "*.tsx" # assigneeUserId 사용처 확인 rg -C 3 "assigneeUserId" -g "*.ts" -g "*.tsx"
| const res = await api.put( | ||
| `/${process.env.NEXT_PUBLIC_TEAM_ID}/cards/${cardId}`, | ||
| payload, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
환경변수 검증 및 오류 처리 추가 필요
환경변수가 존재하지 않을 경우에 대한 검증과 API 요청 실패에 대한 오류 처리가 필요합니다.
export async function putCard(
payload: CardModifyFormData,
cardId: number,
): Promise<ApiResponse> {
+ if (!process.env.NEXT_PUBLIC_TEAM_ID) {
+ throw new Error('NEXT_PUBLIC_TEAM_ID 환경변수가 설정되지 않았습니다.')
+ }
+
+ try {
const res = await api.put(
`/${process.env.NEXT_PUBLIC_TEAM_ID}/cards/${cardId}`,
payload,
)
return res.data
+ } catch (error) {
+ console.error('카드 수정 실패:', error)
+ throw error
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const res = await api.put( | |
| `/${process.env.NEXT_PUBLIC_TEAM_ID}/cards/${cardId}`, | |
| payload, | |
| export async function putCard( | |
| payload: CardModifyFormData, | |
| cardId: number, | |
| ): Promise<ApiResponse> { | |
| if (!process.env.NEXT_PUBLIC_TEAM_ID) { | |
| throw new Error('NEXT_PUBLIC_TEAM_ID 환경변수가 설정되지 않았습니다.') | |
| } | |
| try { | |
| const res = await api.put( | |
| `/${process.env.NEXT_PUBLIC_TEAM_ID}/cards/${cardId}`, | |
| payload, | |
| ) | |
| return res.data | |
| } catch (error) { | |
| console.error('카드 수정 실패:', error) | |
| throw error | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/app/features/dashboard_Id/api/putCard.ts around lines 14 to 16, add
validation to check if the environment variable NEXT_PUBLIC_TEAM_ID is defined
before making the API call. If it is missing, throw or handle an appropriate
error. Additionally, wrap the API put request in a try-catch block to handle any
request failures and provide meaningful error handling or logging.
| const modalRoot = document.getElementById('modal-root') | ||
| if (!modalRoot) return null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
SSR 호환성을 위한 useEffect 사용 고려
현재 구현은 클라이언트 사이드에서만 동작하며, SSR 중에는 document에 접근할 수 없어 오류가 발생할 수 있습니다.
+import { useEffect, useState } from 'react'
import { createPortal } from 'react-dom'
interface ModalProps {
children: React.ReactNode
}
export default function CardModal({ children }: ModalProps) {
- const modalRoot = document.getElementById('modal-root')
- if (!modalRoot) return null
+ const [modalRoot, setModalRoot] = useState<Element | null>(null)
+
+ useEffect(() => {
+ setModalRoot(document.getElementById('modal-root'))
+ }, [])
+
+ if (!modalRoot) return null📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const modalRoot = document.getElementById('modal-root') | |
| if (!modalRoot) return null | |
| import { useEffect, useState } from 'react' | |
| import { createPortal } from 'react-dom' | |
| interface ModalProps { | |
| children: React.ReactNode | |
| } | |
| export default function CardModal({ children }: ModalProps) { | |
| const [modalRoot, setModalRoot] = useState<Element | null>(null) | |
| useEffect(() => { | |
| setModalRoot(document.getElementById('modal-root')) | |
| }, []) | |
| if (!modalRoot) return null | |
| return createPortal( | |
| children, | |
| modalRoot | |
| ) | |
| } |
🤖 Prompt for AI Agents
In src/app/features/dashboard_Id/Card/cardModal/CardModal.tsx around lines 7 to
8, the code directly accesses document.getElementById which causes errors during
server-side rendering (SSR) because document is not defined. To fix this, move
the modalRoot assignment inside a useEffect hook and store it in a state
variable so that it only runs on the client side after the component mounts,
ensuring SSR compatibility.
| // const colorIndex = getColor(tag, bgColors.length) | ||
| // getColors함수 사용하면 NaN값이 떠서 작동을 안함.. 원래 문제 없었는데 이유를 모르겠음. | ||
| const hash = tag | ||
| .split('') | ||
| .reduce((acc, char) => acc + char.charCodeAt(0), 0) | ||
| const colorIndex = hash % 4 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
코드 중복 및 getColor 함수 문제 해결 필요
수동 해시 계산 로직이 TagsCanDelete.tsx와 중복됩니다. getColor 함수에서 NaN 문제가 발생한다면 해당 함수를 수정하는 것이 더 나은 접근입니다.
다음과 같이 개선할 수 있습니다:
- // const colorIndex = getColor(tag, bgColors.length)
- // getColors함수 사용하면 NaN값이 떠서 작동을 안함.. 원래 문제 없었는데 이유를 모르겠음.
- const hash = tag
- .split('')
- .reduce((acc, char) => acc + char.charCodeAt(0), 0)
- const colorIndex = hash % 4
+ const colorIndex = getColor(tag, bgColors.length)getColor 함수의 NaN 문제를 먼저 해결하고, 공통 해시 함수를 만들어 중복을 제거하세요.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // const colorIndex = getColor(tag, bgColors.length) | |
| // getColors함수 사용하면 NaN값이 떠서 작동을 안함.. 원래 문제 없었는데 이유를 모르겠음. | |
| const hash = tag | |
| .split('') | |
| .reduce((acc, char) => acc + char.charCodeAt(0), 0) | |
| const colorIndex = hash % 4 | |
| const colorIndex = getColor(tag, bgColors.length) |
🤖 Prompt for AI Agents
In src/app/features/dashboard_Id/Card/Tags.tsx around lines 18 to 23, the manual
hash calculation duplicates logic found in TagsCanDelete.tsx and the getColor
function currently causes NaN errors. To fix this, first debug and correct the
getColor function to prevent NaN results, then extract the hash calculation into
a shared utility function used by both components to eliminate code duplication
and ensure consistent color indexing.
| const hash = tag | ||
| .split('') | ||
| .reduce((acc, char) => acc + char.charCodeAt(0), 0) | ||
| const colorIndex = hash % 4 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
코드 중복 제거 필요
Tags.tsx와 동일한 해시 계산 로직이 중복됩니다. 공통 유틸리티 함수로 추출하세요.
공통 함수를 만들어 중복을 제거하세요:
// utils/tagColorUtils.ts
export function getTagColorIndex(tag: string): number {
const hash = tag
.split('')
.reduce((acc, char) => acc + char.charCodeAt(0), 0)
return hash % 4
}그 후 두 컴포넌트에서 사용:
- const hash = tag
- .split('')
- .reduce((acc, char) => acc + char.charCodeAt(0), 0)
- const colorIndex = hash % 4
+ const colorIndex = getTagColorIndex(tag)🤖 Prompt for AI Agents
In src/app/features/dashboard_Id/Card/TagsCanDelete.tsx around lines 23 to 26,
the hash calculation logic for determining colorIndex is duplicated from
Tags.tsx. Extract this logic into a shared utility function, for example
getTagColorIndex in a new file like utils/tagColorUtils.ts, which takes a tag
string and returns the color index. Then replace the duplicated code in both
components by importing and using this utility function to remove redundancy.
| backgroundColor: backgroundColor, | ||
| color: textColor, | ||
| }} | ||
| onClick={() => setTags(tags.filter((t) => t !== tag))} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
키보드 접근성 개선 필요
태그 삭제가 마우스 클릭으로만 가능합니다. 키보드 접근성을 위해 Enter/Space 키 지원을 추가하세요.
<span
key={tag}
className="inline-block whitespace-nowrap rounded-4 px-9 pb-3 pt-5"
+ role="button"
+ tabIndex={0}
style={{
backgroundColor: backgroundColor,
color: textColor,
}}
onClick={() => setTags(tags.filter((t) => t !== tag))}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault()
+ setTags(tags.filter((t) => t !== tag))
+ }
+ }}
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| onClick={() => setTags(tags.filter((t) => t !== tag))} | |
| <span | |
| key={tag} | |
| className="inline-block whitespace-nowrap rounded-4 px-9 pb-3 pt-5" | |
| role="button" | |
| tabIndex={0} | |
| style={{ | |
| backgroundColor: backgroundColor, | |
| color: textColor, | |
| }} | |
| onClick={() => setTags(tags.filter((t) => t !== tag))} | |
| onKeyDown={(e) => { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault() | |
| setTags(tags.filter((t) => t !== tag)) | |
| } | |
| }} | |
| > | |
| {tag} | |
| </span> |
🤖 Prompt for AI Agents
In src/app/features/dashboard_Id/Card/TagsCanDelete.tsx at line 41, the tag
deletion is currently triggered only by mouse clicks via the onClick handler. To
improve keyboard accessibility, add keyboard event handling to support tag
deletion when the Enter or Space key is pressed. Implement an onKeyDown handler
that checks for these keys and triggers the same tag removal logic as the
onClick event.
| name: string | ||
| imageUrl: string | null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
타입 정의와 런타임 로직 불일치
props가 required로 정의되어 있지만 런타임에서는 optional로 처리됩니다. 타입 정의를 실제 사용법에 맞게 수정하세요.
type AvatarProps = {
size?: number
- name: string
- imageUrl: string | null
+ name?: string
+ imageUrl?: string | null
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| name: string | |
| imageUrl: string | null | |
| type AvatarProps = { | |
| size?: number | |
| name?: string | |
| imageUrl?: string | null | |
| } |
🤖 Prompt for AI Agents
In src/app/shared/components/common/Avatar.tsx around lines 11 to 12, the props
'name' and 'imageUrl' are currently defined as required in the type but are
treated as optional in the runtime logic. Update the type definitions to mark
these props as optional to match their actual usage in the component, ensuring
consistency between type definitions and runtime behavior.
| const nickname = useRef<string>() | ||
| const profileImageUrl = useRef<string | null>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
useRef 부적절한 사용
useRef를 단순한 값 저장용으로 사용하고 있습니다. 렌더링 간 값 유지가 필요하지 않다면 일반 변수를 사용하세요.
- const nickname = useRef<string>()
- const profileImageUrl = useRef<string | null>()
+ let nickname: string
+ let profileImageUrl: string | null그리고 .current 접근을 제거:
if (name) {
- nickname.current = name
- profileImageUrl.current = imageUrl
+ nickname = name
+ profileImageUrl = imageUrl
} else {
- nickname.current = user.nickname
- profileImageUrl.current = user.profileImageUrl
+ nickname = user.nickname
+ profileImageUrl = user.profileImageUrl
}🤖 Prompt for AI Agents
In src/app/shared/components/common/Avatar.tsx at lines 36-37, useRef is used to
store simple values that do not need to persist across renders. Replace useRef
with regular variables for nickname and profileImageUrl, and remove all .current
property accesses accordingly to simplify the code and avoid misuse of useRef.
| ...data, | ||
| columnId: columnId, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
선택된 컬럼 ID 사용 로직 오류
현재 코드는 선택된 컬럼(selectedColumn)을 무시하고 기존 카드의 columnId를 사용하고 있습니다. 사용자가 다른 컬럼을 선택했을 때 해당 변경사항이 반영되지 않습니다.
const payload: CardModifyFormData = {
...data,
- columnId: columnId,
+ columnId: selectedColumn.columnId,
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ...data, | |
| columnId: columnId, | |
| } | |
| const payload: CardModifyFormData = { | |
| ...data, | |
| columnId: selectedColumn.columnId, | |
| } |
🤖 Prompt for AI Agents
In src/app/features/dashboard_Id/Card/cardFormModals/ModifyCardForm.tsx around
lines 114 to 116, the code incorrectly uses the existing card's columnId instead
of the selectedColumn's ID, causing changes in column selection to be ignored.
Update the code to assign columnId from the selectedColumn variable so that user
changes to the column selection are properly reflected.
| if (!data.dueDate) delete payload.dueDate | ||
| if (!data.imageUrl || !preview) delete payload.imageUrl // delete로 아예 필드의 해당 key를 지워야, 서버가 "없음"으로 인식함.. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
성능 최적화: delete 연산자 대신 undefined 할당 사용
Static analysis에서 지적한 대로 delete 연산자는 성능에 영향을 줄 수 있습니다. undefined 할당을 사용하는 것이 더 효율적입니다.
- if (!data.dueDate) delete payload.dueDate
- if (!data.imageUrl || !preview) delete payload.imageUrl // delete로 아예 필드의 해당 key를 지워야, 서버가 "없음"으로 인식함..
+ if (!data.dueDate) payload.dueDate = undefined
+ if (!data.imageUrl || !preview) payload.imageUrl = undefined📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!data.dueDate) delete payload.dueDate | |
| if (!data.imageUrl || !preview) delete payload.imageUrl // delete로 아예 필드의 해당 key를 지워야, 서버가 "없음"으로 인식함.. | |
| if (!data.dueDate) payload.dueDate = undefined | |
| if (!data.imageUrl || !preview) payload.imageUrl = undefined |
🤖 Prompt for AI Agents
In src/app/features/dashboard_Id/Card/cardFormModals/ModifyCardForm.tsx at lines
118-119, replace the use of the delete operator on payload keys with assigning
undefined to those keys instead, as this improves performance by avoiding the
costly delete operation while still indicating the absence of the field to the
server.
| <div> | ||
| 그리드로 관리하기 / 컬럼명은 따로 컴포넌트로 만들어 빼기 | ||
| <div>{column.title}</div> | ||
| <Tags tags={tags} /> | ||
| <p>설명~~~~~~~~</p> | ||
| <div> | ||
| <div>{assignee.nickname}</div> | ||
| <div>{dueDate}</div> | ||
| </div> | ||
| </div> | ||
| <div> 이미지 있으면 이미지</div> | ||
| <div>댓글 컴포넌트</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
개발용 플레이스홀더 코드 정리
현재 플레이스홀더 텍스트와 미완성된 구현이 포함되어 있습니다. 실제 UI 구현으로 교체하거나 TODO 주석으로 정리하는 것이 좋겠습니다.
개발 완료 후 이 섹션을 실제 컴포넌트들로 교체해야 합니다:
ColumnTitle컴포넌트 사용MyAssignee컴포넌트 사용- 이미지 표시 로직 구현
- 댓글 컴포넌트 구현
🤖 Prompt for AI Agents
In src/app/features/dashboard_Id/Card/cardModal/CardContent.tsx around lines 65
to 76, replace the placeholder text and incomplete UI elements with actual
components or add TODO comments. Specifically, use the ColumnTitle component for
the column title, MyAssignee component for the assignee display, implement the
image display logic if an image exists, and replace the comment placeholder with
the actual comments component. This cleanup will prepare the code for final UI
implementation.
Insung-Jo
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
구현 수고 많으셨습니다! 이제 얼마 안 남았으니 마지막까지 힘내봐용 :D
| const modalRoot = document.getElementById('modal-root') | ||
| if (!modalRoot) return null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코드 래빗의 리뷰 처럼 추후에 SSR 도입을 하신다면 고려할 수 있는 내용인 거 같습니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵, 제 모달에 SSR가 적합할지 나중에 한번 고민해보겠습니당
LeeCh0129
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
카드 수정 모달 수고많으셨습니다~ 마지막 까지 화이팅해보시죠 👍
📌 변경 사항 개요
✨ 요약
📝 상세 내용
payload 목록 타입
🔗 관련 이슈
🖼️ 스크린샷
2025-06-20.9.31.03.mov
✅ 체크리스트
💡 참고 사항
Summary by CodeRabbit
신규 기능
기능 개선
버그 수정
리팩터링 및 스타일
기타